/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/

#include "pch.h"
#include <StudentTable.h>

namespace ClassRoom
{

enum field_ids
{
    id_field = 1000,
    name_field,
    month_field,
    day_field,
    year_field,
    hobbies_field,
    score_field,
    image_field,
    notes_field

};

enum index_ids
{
    primary_index = 2000,
    score_index,
    date_of_birth_index
};

namespace s = test_wrappers::storage;

const std::wstring StudentTable::_name = L"Student";

StudentTable::StudentTable() :
    _idFieldId(id_field),
    _nameFieldId(name_field),
    _mmFieldId(month_field),
    _ddFieldId(day_field),
    _yyFieldId(year_field),
    _hobbiesFieldId(hobbies_field),
    _scoreFieldId(score_field),
    _imageFieldId(image_field),
    _notesFieldId(notes_field)
{
}

void StudentTable::Initialize(
    const std::shared_ptr<test_wrappers::storage::db_jet_engine>& dbEngine
    )
{
    _dbEngine = dbEngine;

    // create field definitions...
    s::field_definition_t<unsigned int> idField(_idFieldId, L"Id");
    idField.setAutoIncrement(true);

    s::string_field nameField(_nameFieldId, L"Name");

    s::field_definition_t<unsigned int> mmField(_mmFieldId, L"Month");
    s::field_definition_t<unsigned int> ddField(_ddFieldId, L"Day");
    s::field_definition_t<unsigned int> yyField(_yyFieldId, L"Year");

    s::string_field hobbiesField(_hobbiesFieldId, L"Hobbies");

    s::field_definition_t<unsigned int> scoreField(_scoreFieldId, L"Score");

    s::blob_field imageField(_imageFieldId, L"Image");
    s::blob_field notesField(_notesFieldId, L"Notes");

    // create index definitions...
    s::index_definition primaryIndex(primary_index, L"PK_Index", s::primary_index | s::unique_index);
    primaryIndex.addIndexField(s::index_field_definition(_idFieldId.id, s::ascending_index_field));

    // create index definitions...
    s::index_definition scoreIndex(score_index, L"Score_Index", s::unique_index);
    scoreIndex.addIndexField(s::index_field_definition(_scoreFieldId.id, s::ascending_index_field));

    // create index definitions...
    s::index_definition dateOfBirthIndex(date_of_birth_index, L"DateOfBirth_Index", s::default_index);
    dateOfBirthIndex.addIndexField(s::index_field_definition(_yyFieldId.id, s::ascending_index_field));
    dateOfBirthIndex.addIndexField(s::index_field_definition(_mmFieldId.id, s::ascending_index_field));
    dateOfBirthIndex.addIndexField(s::index_field_definition(_ddFieldId.id, s::ascending_index_field));

    // define the table...
    s::table_definition tableDefinition(_name);

    // fields...
    tableDefinition.addField(idField);
    tableDefinition.addField(nameField);
    tableDefinition.addField(mmField);
    tableDefinition.addField(ddField);
    tableDefinition.addField(yyField);
    tableDefinition.addField(hobbiesField);
    tableDefinition.addField(scoreField);
    tableDefinition.addField(imageField);
    tableDefinition.addField(notesField);

    // indices...
    tableDefinition.addIndex(primaryIndex);
    tableDefinition.addIndex(scoreIndex);
    tableDefinition.addIndex(dateOfBirthIndex);

    // create the table in the database...
    _tableInstance = _dbEngine->instantiateTable(tableDefinition);
}

void StudentTable::InsertOrUpdate(
    const unsigned int& id,
    const std::wstring& name,
    const unsigned int& mm,
    const unsigned int& dd,
    const unsigned int& yy,
    const std::wstring& hobbies,
    const unsigned int& score
    )
{
    // prepare the values to insert/update...
    s::tuple_values recordValues = _tableInstance.createValues();
    recordValues.field(_nameFieldId)->setValue(name);
    recordValues.field(_mmFieldId)->setValue(mm);
    recordValues.field(_ddFieldId)->setValue(dd);
    recordValues.field(_yyFieldId)->setValue(yy);
    recordValues.field(_hobbiesFieldId)->setValue(hobbies);
    recordValues.field(_scoreFieldId)->setValue(score);

    // decide if it is a new record or not...

    // select
    std::vector<s::field_id_b> selectedFields;
    selectedFields.push_back(_idFieldId);

    // where
    s::select_criteria selectCriteria = _tableInstance.index(primary_index)->createSelectCriteria(s::comparison_operator::equal_operator);
    selectCriteria.values().field(_idFieldId)->setValue(id);

    // execute
    std::vector<s::tuple_values> readRows;
    _dbEngine->read(_tableInstance, selectCriteria, selectedFields, readRows);

    bool newRecord = readRows.size() == 0;

    if (newRecord)
    {
        // we just assume that the image information is not present at the time of insertion...
        recordValues.field(_imageFieldId)->setValue(s::binary_blob(0));
        recordValues.field(_notesFieldId)->setValue(s::binary_blob(0));

        _dbEngine->insert(_tableInstance, recordValues);
    }
    else
    {
        _dbEngine->update(_tableInstance, selectCriteria, recordValues);
    }
}

void StudentTable::UpdateImage(
    const unsigned int& id,
    s::binary_blob& image
    )
{
    // prepare the values to insert/update...
    s::tuple_values recordValues = _tableInstance.createValues();
    recordValues.field(_imageFieldId)->setValue(image);

    // where
    s::select_criteria selectCriteria = _tableInstance.index(primary_index)->createSelectCriteria(s::comparison_operator::equal_operator);
    selectCriteria.values().field(_idFieldId)->setValue(id);

    // execute
    _dbEngine->update(_tableInstance, selectCriteria, recordValues);
}

void StudentTable::AppendNote(
    const unsigned int& id,
    s::binary_blob& newNote
    )
{
    // prepare the values to insert/update...
    s::tuple_values recordValues = _tableInstance.createValues();
    recordValues.field(_notesFieldId)->setValue(newNote);
    recordValues.field(_notesFieldId)->setAppendOnUpdate(true);

    // where
    s::select_criteria selectCriteria = _tableInstance.index(primary_index)->createSelectCriteria(s::comparison_operator::equal_operator);
    selectCriteria.values().field(_idFieldId)->setValue(id);

    // execute
    _dbEngine->update(_tableInstance, selectCriteria, recordValues);
}

void StudentTable::ReadById(
    const unsigned int& id,
    std::vector<StudentRecord>& records
    )
{
    // select
    std::vector<s::field_id_b> selectedFields;
    selectedFields.push_back(_idFieldId);
    selectedFields.push_back(_nameFieldId);
    selectedFields.push_back(_mmFieldId);
    selectedFields.push_back(_ddFieldId);
    selectedFields.push_back(_yyFieldId);
    selectedFields.push_back(_hobbiesFieldId);
    selectedFields.push_back(_scoreFieldId);

    // where
    s::select_criteria selectCriteria = _tableInstance.index(primary_index)->createSelectCriteria(s::comparison_operator::equal_operator);
    selectCriteria.values().field(_idFieldId)->setValue(id);

    // execute
    std::vector<s::tuple_values> readRows;
    _dbEngine->read(_tableInstance, selectCriteria, selectedFields, readRows);

    // pack into vector
    PackResult(_tableInstance, readRows, records);
}

void StudentTable::ReadImageById(
    const unsigned int& id,
    s::binary_blob& image
    )
{
    // select
    std::vector<s::field_id_b> selectedFields;
    selectedFields.push_back(_idFieldId);
    selectedFields.push_back(_imageFieldId);

    // where
    s::select_criteria selectCriteria = _tableInstance.index(primary_index)->createSelectCriteria(s::comparison_operator::equal_operator);
    selectCriteria.values().field(_idFieldId)->setValue(id);

    // execute
    std::vector<s::tuple_values> readRows;
    _dbEngine->read(_tableInstance, selectCriteria, selectedFields, readRows);

    // extract (we expect 1 row)
    for (auto &row : readRows)
    {
        std::shared_ptr<const std::vector<std::shared_ptr<s::field_value>>> values = row.getValues();
        for (auto &value : *values)
        {
            s::field_id userFieldId = _tableInstance.tupleInstance()->field(value->fieldInstanceId())->userFieldId();

            if( userFieldId == _imageFieldId.id )
            {
                std::shared_ptr<s::field_value_t<s::binary_blob>> valueT = std::dynamic_pointer_cast<s::field_value_t<s::binary_blob>>(value);
                image = valueT->value();
                break;
            }
        }
        break;
    }
}

void StudentTable::ReadNotesById(
    const unsigned int& id,
    s::binary_blob& allNotes
    )
{
    // select
    std::vector<s::field_id_b> selectedFields;
    selectedFields.push_back(_idFieldId);
    selectedFields.push_back(_notesFieldId);

    // where
    s::select_criteria selectCriteria = _tableInstance.index(primary_index)->createSelectCriteria(s::comparison_operator::equal_operator);
    selectCriteria.values().field(_idFieldId)->setValue(id);

    // execute
    std::vector<s::tuple_values> readRows;
    _dbEngine->read(_tableInstance, selectCriteria, selectedFields, readRows);

    // extract (we expect 1 row)
    for (auto &row : readRows)
    {
        std::shared_ptr<const std::vector<std::shared_ptr<s::field_value>>> values = row.getValues();
        for (auto &value : *values)
        {
            s::field_id userFieldId = _tableInstance.tupleInstance()->field(value->fieldInstanceId())->userFieldId();

            if( userFieldId == _notesFieldId.id )
            {
                std::shared_ptr<s::field_value_t<s::binary_blob>> valueT = std::dynamic_pointer_cast<s::field_value_t<s::binary_blob>>(value);
                allNotes = valueT->value();
                break;
            }
        }
        break;
    }
}

void StudentTable::ReadByYear(
    const unsigned int& year,
    std::vector<StudentRecord>& records
    )
{
    // select
    std::vector<s::field_id_b> selectedFields;
    selectedFields.push_back(_idFieldId);
    selectedFields.push_back(_nameFieldId);
    selectedFields.push_back(_mmFieldId);
    selectedFields.push_back(_ddFieldId);
    selectedFields.push_back(_yyFieldId);
    selectedFields.push_back(_hobbiesFieldId);
    selectedFields.push_back(_scoreFieldId);

    // where
    s::select_criteria selectCriteria = _tableInstance.index(date_of_birth_index)->createSelectCriteria(s::comparison_operator::equal_or_great_than_operator);
    selectCriteria.values().field(_yyFieldId)->setValue(year);

    // execute
    std::vector<s::tuple_values> readRows;
    _dbEngine->read(_tableInstance, selectCriteria, selectedFields, readRows);

    // pack into vector
    PackResult(_tableInstance, readRows, records);
}

void StudentTable::ReadByScore(
    const unsigned int& score,
    std::vector<StudentRecord>& records
    )
{
    // select
    std::vector<s::field_id_b> selectedFields;
    selectedFields.push_back(_idFieldId);
    selectedFields.push_back(_nameFieldId);
    selectedFields.push_back(_mmFieldId);
    selectedFields.push_back(_ddFieldId);
    selectedFields.push_back(_yyFieldId);
    selectedFields.push_back(_hobbiesFieldId);
    selectedFields.push_back(_scoreFieldId);

    // where
    s::select_criteria selectCriteria = _tableInstance.index(score_index)->createSelectCriteria(s::comparison_operator::equal_operator);
    selectCriteria.values().field(_scoreFieldId)->setValue(score);

    // execute
    std::vector<s::tuple_values> readRows;
    _dbEngine->read(_tableInstance, selectCriteria, selectedFields, readRows);

    // pack into vector
    PackResult(_tableInstance, readRows, records);
}

void StudentTable::ReadAll(
    std::vector<StudentRecord>& records
    )
{
    // select
    std::vector<s::field_id_b> selectedFields;
    selectedFields.push_back(_idFieldId);
    selectedFields.push_back(_nameFieldId);
    selectedFields.push_back(_mmFieldId);
    selectedFields.push_back(_ddFieldId);
    selectedFields.push_back(_yyFieldId);
    selectedFields.push_back(_hobbiesFieldId);
    selectedFields.push_back(_scoreFieldId);

    // execute
    std::vector<s::tuple_values> readRows;
    _dbEngine->readAll(_tableInstance, primary_index, selectedFields, readRows);

    // pack into vector
    PackResult(_tableInstance, readRows, records);
}

void StudentTable::DeleteById(
    const unsigned int& id
    )
{
    // where
    s::select_criteria selectCriteria = _tableInstance.index(primary_index)->createSelectCriteria(s::comparison_operator::equal_operator);
    selectCriteria.values().field(_idFieldId)->setValue(id);

    // execute
    _dbEngine->dispose(_tableInstance, selectCriteria);
}

void StudentTable::PackResult(
    const s::table_instance& tableInstance,
    const std::vector<s::tuple_values>& readRows,
    std::vector<StudentRecord>& records
    )
{
    for (auto &row : readRows)
    {
        StudentRecord studentRecord;

        std::shared_ptr<const std::vector<std::shared_ptr<s::field_value>>> values = row.getValues();
        for (auto &value : *values)
        {
            s::field_id userFieldId = tableInstance.tupleInstance()->field(value->fieldInstanceId())->userFieldId();

            if( userFieldId == _idFieldId.id )
            {
                std::shared_ptr<s::field_value_t<unsigned int>> valueT = std::dynamic_pointer_cast<s::field_value_t<unsigned int>>(value);
                studentRecord.id = valueT->value();
                continue;
            }
            if( userFieldId == _nameFieldId.id )
            {
                std::shared_ptr<s::field_value_t<std::wstring>> valueT = std::dynamic_pointer_cast<s::field_value_t<std::wstring>>(value);
                studentRecord.name = valueT->value();
                continue;
            }
            if( userFieldId == _mmFieldId.id )
            {
                std::shared_ptr<s::field_value_t<unsigned int>> valueT = std::dynamic_pointer_cast<s::field_value_t<unsigned int>>(value);
                studentRecord.mm = valueT->value();
                continue;
            }
            if( userFieldId == _ddFieldId.id )
            {
                std::shared_ptr<s::field_value_t<unsigned int>> valueT = std::dynamic_pointer_cast<s::field_value_t<unsigned int>>(value);
                studentRecord.dd = valueT->value();
                continue;
            }
            if( userFieldId == _yyFieldId.id )
            {
                std::shared_ptr<s::field_value_t<unsigned int>> valueT = std::dynamic_pointer_cast<s::field_value_t<unsigned int>>(value);
                studentRecord.yy = valueT->value();
                continue;
            }
            if( userFieldId == _hobbiesFieldId.id )
            {
                std::shared_ptr<s::field_value_t<std::wstring>> valueT = std::dynamic_pointer_cast<s::field_value_t<std::wstring>>(value);
                studentRecord.hobbies = valueT->value();
                continue;
            }
            if( userFieldId == _scoreFieldId.id )
            {
                std::shared_ptr<s::field_value_t<unsigned int>> valueT = std::dynamic_pointer_cast<s::field_value_t<unsigned int>>(value);
                studentRecord.score = valueT->value();
                continue;
            }
        }
        records.push_back(studentRecord);
    }
}

}// ClassRoom